/*-
 * Copyright 2012 Guram Dukashvili
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
//---------------------------------------------------------------------------
#include "sys.h"
#include "array.h"
#include "str.h"
#include "cdc256.h"
#include "mutex.h"
#include "thread.h"
#include "idx.h"
#if HAVE_LINUX_UNISTD_H
#include <linux/unistd.h>
#endif
#if HAVE_SYSCALL_H
#include <syscall.h>
#endif
//---------------------------------------------------------------------------
void microacid_system_test_and_init(void)
{
  portable_stat_t st;
  if( sizeof(st.st_size) < 8 ){
    fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
  }
  if( sizeof(char) != 1 ){
    fprintf(stderr,"unsupported system arch, sizeof(char) != 1\n");
    abort();
  }
  if( sizeof(intptr_t) > 8 ){
    fprintf(stderr,"unsupported system arch, sizeof(intptr_t) > 8\n");
    abort();
  }
  if( sizeof(intptr_t) < 4 ){
    fprintf(stderr,"unsupported system arch, sizeof(intptr_t) < 4\n");
    abort();
  }
  if( sizeof(void *) > 8 ){
    fprintf(stderr,"unsupported system arch, sizeof(void *) > 8\n");
    abort();
  }
  if( sizeof(void *) < 4 ){
    fprintf(stderr,"unsupported system arch, sizeof(void *) < 4\n");
    abort();
  }
  if( (bool) (!false) != true ){
    fprintf(stderr,"unsupported system arch, !bool\n");
    abort();
  }
  if( (1 > 0) != 1 ){
    fprintf(stderr,"unsupported system arch, 1 > 0 != 1\n");
    abort();
  }
  if( sizeof(portable_off_t) < 8 ){
    fprintf(stderr,"system not support large files, sizeof(portable_off_t) < 8\n");
    abort();
  }
#if __WIN32__
  // set current user code page locale for start working mbxx funtions and strcoll _stricoll
  setlocale(LC_ALL,".OCP");
  setlocale(LC_ALL,".ACP");
#endif
  mutex_init_runtime();
  thread_init_runtime();
  index_init_runtime();
}
//---------------------------------------------------------------------------
void microacid_system_destroy(void)
{
  index_destroy_runtime();
  thread_destroy_runtime();
  mutex_destroy_runtime();
}
//---------------------------------------------------------------------------
/**
    Lookup table based integer LOG2
*/
static __inline__ unsigned ilog2_LUT(uintptr_t x)
{

  static const char idx[256] = {
     0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
     4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
     5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
     5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
  };

  unsigned l = 0;

#if SIZEOF_INTPTR_T > 4
  if( x & UINT64_C(0xffffffff00000000) ){
    l += 32;
    x >>= 32;
  }
#endif

  if( x & 0xffff0000 ){
    l += 16;
    x >>= 16;
  }

  if( x & 0xff00 ){
    l += 8;
    x >>= 8;
  }

  return l + idx[x];
}

uintptr_t ge2(uintptr_t n)
{
  uintptr_t a = UINTPTR_C(1) << ilog2_LUT(n);
  return a < n ? a << 1 : a;
}
//---------------------------------------------------------------------------
uintptr_t le2(uintptr_t n)
{
  return UINTPTR_C(1) << ilog2_LUT(n);
}
//---------------------------------------------------------------------------
void print_new_signature()
{
  struct cdc256_data ctx;
  struct cdc256 b;
  int64_t t = get_time();

  cdc256_init(&ctx);
  cdc256_update(&ctx,&t,sizeof(t));
  cdc256_final(&ctx,&b);
  cdc256_print(stderr,&b);
}
//---------------------------------------------------------------------------
#define EPOCH_BIAS UINT64_C(11644473600)
//---------------------------------------------------------------------------
#if !HAVE_GETTIMEOFDAY
int gettimeofday(struct timeval * tvp, struct timezone * tzp)
{
  ULARGE_INTEGER sti;
  GetSystemTimeAsFileTime((FILETIME *) &sti);

  sti.QuadPart -= EPOCH_BIAS * 10000000u; // January 1, 1970 (UTC) - January 1, 1601 (UTC)

  tvp->tv_sec = sti.QuadPart / 10000000u;
  tvp->tv_usec = (sti.QuadPart % 10000000u) / 10u; //(sti.QuadPart - tvp->tv_sec * UINT64_C(10000000)) / 10u;

  if( tzp != NULL ){
#if HAVE_STRUCT__TIMEB64
    struct __timeb64 tbb;
#elif HAVE_STRUCT_TIMEB
    struct timeb tbb;
#elif HAVE_STRUCT__TIMEB
    struct _timeb tbb;
#endif
#if HAVE_FTIME
#ifdef __BORLANDC__
    ::ftime(&tbb);
#else
    ftime(&tbb);
#endif
#elif HAVE__FTIME
    _ftime(&tbb);
#elif HAVE__FTIME64
    _ftime(&tbb);
#endif
    tzp->tz_minuteswest = tbb.timezone;
    tzp->tz_dsttime = 0;//tbb.dstflag == 0 ? 0 : 1;
//    tzp->tz_minuteswest = -(int) (((st2i.QuadPart - sti.QuadPart) / 10000000u) / 60u);
//    tzp->tz_dsttime = 0;
  }
  return 0;
}
#endif
//---------------------------------------------------------------------------
#if !HAVE_TIMEGM
time_t timegm(struct tm * t)
{
  SYSTEMTIME system_time;
  ULARGE_INTEGER sti;

  system_time.wYear = t->tm_year + 1900;
  system_time.wMonth = t->tm_mon + 1;
  system_time.wDay = t->tm_mday;
  system_time.wHour = t->tm_hour;
  system_time.wMinute = t->tm_min;
  system_time.wSecond = t->tm_sec;
  system_time.wMilliseconds = 0;
  system_time.wDayOfWeek = 0;


  if( SystemTimeToFileTime(&system_time,(FILETIME *) &sti) == 0 ){
    errno = EINVAL;
    return -1;
  }

  sti.QuadPart /= 10000000u;
  return (time_t) (sti.QuadPart - EPOCH_BIAS);
}
#endif
//---------------------------------------------------------------------------
int64_t get_time(void)
{
  struct timeval t;
  int64_t a = -1;

  if( gettimeofday(&t,NULL) == 0 )
    a = (uint64_t) t.tv_sec * 1000000u + t.tv_usec;

  return a;
}
//---------------------------------------------------------------------------
int64_t get_local_time(void)
{
  struct timeval tv;
  struct timezone tz;

  int64_t a = -1;

  if( gettimeofday(&tv,&tz) == 0 )
    a = (uint64_t) tv.tv_sec * 1000000u + tv.tv_usec + getgmtoffset();
  return a;
}
//---------------------------------------------------------------------------
int64_t getgmtoffset(void)
{
#if __WIN32__
  TIME_ZONE_INFORMATION tzinfo;
  if( GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_INVALID ){
    errno = EINVAL;
    return -1;
  }
  return -tzinfo.Bias * 60 * (int64_t) 1000000;
#else
  time_t t;
  time(&t);
  struct tm * ltp = localtime(&t);
  time_t lt = mktime(ltp);
  return (lt - t) * (int64_t) 1000000;
#endif
}
//---------------------------------------------------------------------------
void time2tm(struct tm * t,int64_t a)
{
#if __WIN32__
  SYSTEMTIME system_time;
  ULARGE_INTEGER sti;
  sti.QuadPart = (a + EPOCH_BIAS * 1000000u) * 10u;

  if( FileTimeToSystemTime((FILETIME *) &sti,&system_time) == 0 ){
    errno = EINVAL;
    return;
  }

  memset(t,0,sizeof(*t));
  t->tm_year = system_time.wYear - 1900;
  t->tm_mon = system_time.wMonth - 1;
  t->tm_mday = system_time.wDay;
  t->tm_hour = system_time.wHour;
  t->tm_min = system_time.wMinute;
  t->tm_sec = system_time.wSecond;
#else
  time_t tt = (time_t) (a / 1000000u);
  *t = *gmtime(&tt);
#endif
}
//---------------------------------------------------------------------------
void get_time_code_string(string * s,int64_t t)
{
  uint64_t
    a = (uint64_t) t / 1000000u,
    days = a / 60 / 60 / 24,
    hours = a / 60 / 60 - days * 24,
    mins = a / 60 - days * 24 * 60 - hours * 60,
    secs = a - days * 24 * 60 * 60 - hours * 60 * 60 - mins * 60,
    msecs = (uint64_t) t % 1000000u;
  string_print(s,"%d:%02d:%02d:%02d.%06d",(int) days,(int) hours,(int) mins,(int) secs,(int) msecs);
}
//---------------------------------------------------------------------------
void get_time_string(string * s,int64_t t)
{
  struct tm tma;
  time2tm(&tma,t);

  string_print(s,
    "%02u.%02u.%04u %02u:%02u:%02u.%06u",
    tma.tm_mday,
    tma.tm_mon + 1,
    tma.tm_year + 1900,
    tma.tm_hour,
    tma.tm_min,
    tma.tm_sec,
    (unsigned int) ((uint64_t) t % 1000000u)
  );
}
//---------------------------------------------------------------------------
#if _MSC_VER
#define MEM_GUARD 0
#define MEM_GUARD_ALIGN 64
#define MEM_GUARD_WINDOW 64
struct mem_guard_block_header {
  uintptr_t size_;
};
#endif
//---------------------------------------------------------------------------
void * salloc(uintptr_t size,void * p)
{
  void * a = p;
#if MEM_GUARD
  uintptr_t asize;
  void * data;
  struct mem_guard_block_header * h;

  if( size == 0 ){
    sfree(p);
    return NULL;
  }
  asize = size + (-size & (MEM_GUARD_ALIGN - 1));
  if( asize == size ) asize += MEM_GUARD_ALIGN;
  //void * a = _aligned_malloc(asize + MEM_GUARD_WINDOW * 2,MEM_GUARD_ALIGN);
#undef malloc
  a = malloc(asize + MEM_GUARD_WINDOW * 2);
  if( a == NULL ){
    if( errno == 0 ) errno = ENOMEM;
    return a;
  }
  data = (uint8_t *) a + MEM_GUARD_WINDOW;
  h = NULL;
  if( p == NULL ){
    memset(data,0xAB,size);
    memset((uint8_t *) data + size,0xED,asize - size);
  }
  else {
    h = (struct mem_guard_block_header *) ((uint8_t *) p - MEM_GUARD_WINDOW);
    if( size > h->size_ ){
      memcpy(data,p,h->size_);
      memset((uint8_t *) data + h->size_,0xAB,size - h->size_);
      memset((uint8_t *) data + size,0xED,asize - size);
    }
    else {
      memcpy(data,p,size);
      memset((uint8_t *) data + size,0xED,asize - size);
    }
    sfree(p);
  }
  h = a;
  h->size_ = size;
  memset((uint8_t *) a + sizeof(struct mem_guard_block_header),0xBB,MEM_GUARD_WINDOW - sizeof(struct mem_guard_block_header));
  memset((uint8_t *) a + MEM_GUARD_WINDOW + asize,0xEE,MEM_GUARD_WINDOW);
  return data;
#else
  errno = 0;
  if( size == 0 ){
    free(p);
    return NULL;
  }
  a = NULL;
  if( p == NULL ){
    a = malloc(size);
  }
  else {
    a = realloc(p,size); // must be used only to reduce memory block
    if( a != p ){
      errno = ENOSYS;
      perror(NULL);
#if COMPILE_RUNTIME_CHECKS
      fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
#endif
      abort();
    }
  }
  if( a == NULL && errno == 0 ) errno = ENOMEM;
  return a;
#endif
}
//---------------------------------------------------------------------------
void sfree(void * p)
{
#if MEM_GUARD
  if( p != NULL ){
    uint8_t * m, * g;
    struct mem_guard_block_header * h =
      (struct mem_guard_block_header *) ((uint8_t *) p - MEM_GUARD_WINDOW);
    uintptr_t asize = h->size_ + (-h->size_ & (MEM_GUARD_ALIGN - 1));
    if( asize == h->size_ ) asize += MEM_GUARD_ALIGN;

    m = (uint8_t *) h + sizeof(struct mem_guard_block_header);
    g = m + MEM_GUARD_WINDOW - sizeof(struct mem_guard_block_header);
    while( m < g ){
      if( *m != 0xBB ){
        fprintf(stderr,"memory corruption detected on size = %"PRIuPTR"\n",h->size_);
        abort();
      }
      m++;
    }

    m = (uint8_t *) p + h->size_;
    g = m + (asize - h->size_);
    while( m < g ){
      if( *m != 0xED ){
        fprintf(stderr,"memory corruption detected on size = %"PRIuPTR"\n",h->size_);
        abort();
      }
      m++;
    }

    m = (uint8_t *) p + asize;
    g = m + MEM_GUARD_WINDOW;
    while( m < g ){
      if( *m != 0xEE ){
        fprintf(stderr,"memory corruption detected on size = %"PRIuPTR"\n",h->size_);
        abort();
      }
      m++;
    }

    memset(h,0xFB,MEM_GUARD_WINDOW + asize + MEM_GUARD_WINDOW);
    free(h);
  }
#else
  free(p);
#endif
}
//---------------------------------------------------------------------------
// path functions
//---------------------------------------------------------------------------
void extract_path(string * path,const char * file_name)
{
  extract_path_name(path,NULL,file_name);
}
//---------------------------------------------------------------------------
void extract_name(string * name,const char * file_name)
{
  extract_path_name(NULL,name,file_name);
}
//---------------------------------------------------------------------------
void extract_path_name(string * path,string * name,const char * file_name)
{
  const char * p = file_name + strlen(file_name);
  for(;;){
    if( p == file_name ) break;
    if( *p == '\\' || *p == '/' ){
      if( path != NULL ) string_copy_c(path,file_name,p - file_name);
      if( name != NULL ) string_copy_c(name,p + 1,0);
      break;
    }
    p--;
  }
}
//---------------------------------------------------------------------------
static __inline__ bool name_filter(const string * name,const string * filter)
{
  string_it ni, fi;
  string_it_init(&ni,(string *) name);
  string_it_init(&fi,(string *) filter);

  while( !string_it_eos(&ni) && !string_it_eos(&fi) ){

    int c = string_it_getc(&fi);

    if( c == '?' ){
      string_it_next(&ni);
      string_it_next(&fi);
    }
    else if( c == '*' ){

      string_it_next(&fi);

      if( string_it_eos(&fi) ){
        string_it_end(&ni);
        break;
      }

      while( !string_it_eos(&ni) && !string_it_eos(&fi)
           && string_it_getc(&ni) != string_it_getc(&fi) ){
        string_it_next(&ni);
        if( string_it_eos(&ni) ) break;
      }

      while( string_it_getc(&ni) == (c = string_it_getc(&fi))
          && c != '*' && c != '?' && c != '[' ){
        string_it_next(&ni);
        string_it_next(&fi);
        if( string_it_eos(&ni) ) break;
      }
    }
    else if( c == '[' ){
      errno = ENOSYS;
      return false;
    }
    else {
      if( string_it_getc(&ni) != c ) break;
      string_it_next(&ni);
      string_it_next(&fi);
    }
  }
  errno = 0;
  return string_it_eos(&ni) && string_it_eos(&fi);
}
//---------------------------------------------------------------------------
void dir_list_init(array * dir_list)
{
  array_init(dir_list);
  array_set_element_size(dir_list,sizeof(string),0);
  dir_list->element_init = string_init;
  dir_list->element_destroy = string_destroy;
  dir_list->element_copy = string_copy;
  dir_list->element_compare = string_compare;
  dir_list->element_xchg = string_xchg;
}
//---------------------------------------------------------------------------
void dir_list_get(array * dir_list,const char * filter,bool sub_dirs,bool sort)
{
  int err = 0;
  DIR * dir;
  string path, mask;

  string_init(&path);
  string_init(&mask);

  extract_path_name(&path,&mask,filter);

  dir = portable_opendir(path.s);
  err = errno;

  if( dir != NULL ){
    portable_stat_t st;
    struct dirent * e;

    errno = 0;

    while( errno == 0 && (e = portable_readdir(dir)) != NULL ){
      string * s;

      if( strcmp(e->d_name,".") == 0
         || strcmp(e->d_name,"..") == 0 ) continue;

      s = array_append_get(dir_list);
      string_copy_c(s,e->d_name,0);

      if( name_filter(s,&mask) ){
        string_vcat_c(s,path.s,PATH_DELIM,s->s,NULL);
        err = errno;

        if( sub_dirs ){
          if( portable_stat(s->s,&st) == 0 ){
            if( (st.st_mode & S_IFDIR) != 0 ){
              string sub_filter;
              string_init(&sub_filter);
              string_vcat_c(&sub_filter,s->s,PATH_DELIM,mask.s,NULL);
              dir_list_get(dir_list,sub_filter.s,true,false);
              if( errno == EPERM ) errno = 0; // just skeep unreachable dir
              string_destroy(&sub_filter);
            }
          }
          else {
            if( errno == ENOENT ) errno = 0; // just skeep strange dir or special file            
          }
        }
      }
      else {
        array_resize(dir_list,dir_list->count - 1);
      }
    }
    err = errno;
    portable_closedir(dir);
  }

  string_destroy(&mask);
  string_destroy(&path);

  if( sort )
    array_sort(dir_list);

  errno = err;
}
//---------------------------------------------------------------------------
// modified system functions with exceptions
//---------------------------------------------------------------------------
DIR * portable_opendir(const char * dir_name)
{
#if _MSC_VER
  DIR * dir = NULL;

  if( dir_name != NULL && dir_name[0] != '\0' ){
    size_t base_length = strlen(dir_name);
    const char * all = /* search pattern must end with suitable wildcard */
        strchr("/\\", dir_name[base_length - 1]) ? "*" : "\\*";

    if( (dir = salloc(sizeof *dir,NULL)) != NULL &&
        (dir->name = salloc(base_length + strlen(all) + 1,NULL)) != NULL ){
      strcat(strcpy(dir->name,dir_name),all);
      if( (dir->handle = _findfirsti64(dir->name, &dir->info)) != -1 ){
        dir->result.d_name = NULL;
      }
      else /* rollback */ {
        DWORD err = GetLastError();
        sfree(dir->name);
        sfree(dir);
        dir = NULL;
        if( err == ERROR_ACCESS_DENIED ) errno = EPERM;
      }
    }
    else /* rollback */ {
      sfree(dir);
      dir   = NULL;
      errno = ENOMEM;
    }
  }
  else {
    errno = EINVAL;
  }
  return dir;
#else
  return opendir(dir_name);
#endif
}
//---------------------------------------------------------------------------
struct dirent * portable_readdir(DIR * dir)
{
#if _MSC_VER
  struct dirent * result = NULL;

  if( dir != NULL && dir->handle != -1 ){
    int r = 0;

    if( dir->result.d_name == NULL || (r = _findnexti64(dir->handle,&dir->info)) != -1 ){
      result         = &dir->result;
      result->d_name = dir->info.name;
    }
    if( r == -1 && errno == ENOENT ) errno = 0;
  }
  else {
    errno = EBADF;
  }
  return result;
#else
  return readdir(dir);
#endif
}
//---------------------------------------------------------------------------
void portable_rewinddir(DIR * dir)
{
#if _MSC_VER
  if( dir != NULL && dir->handle != -1 ){
    _findclose(dir->handle);
    dir->handle = _findfirsti64(dir->name, &dir->info);
    dir->result.d_name = NULL;
  }
  else {
    errno = EBADF;
  }
#else
  rewinddir(dir);
#endif
}
//---------------------------------------------------------------------------
int portable_closedir(DIR * dir)
{
#if _MSC_VER
  int result = -1;

  if( dir != NULL ){
    if( dir->handle != -1 )
      result = _findclose(dir->handle);
    sfree(dir->name);
    sfree(dir);
  }

  if( result == -1 ) /* map all errors to EBADF */
      errno = EBADF;
  return result;
#else
  return closedir(dir);
#endif
}
//---------------------------------------------------------------------------
int portable_stat(const char * file_name,portable_stat_t * st)
{
  int r =
#if HAVE__STAT32I64
  _stat32i64(file_name,st);
#elif HAVE__STAT64
  _stat64(file_name,st);
#elif HAVE__STATI64
  _stati64(file_name,st);
#else
  stat(file_name,st);
#endif
  if( r == 0 ) errno = 0;
  return r;
}
//---------------------------------------------------------------------------
int portable_mkdir(const char * dir_name,mode_t mode,bool make_sub_dirs)
{
#if HAVE_MKDIR
  if( mkdir(dir_name,mode) == -1 ){
#elif HAVE__MKDIR
#if __WIN32__
  if( _mkdir(dir_name) == -1 ){
#else
  if( _mkdir(dir_name,mode) == -1 ){
#endif
#endif
    int err = errno, r;

    portable_stat_t st;

    if( portable_stat(dir_name,&st) != 0 ) return -1;

    if( err == EEXIST ){
      if( (st.st_mode & S_IFDIR) != 0 ) return 0;
      return -1;
    }
    else if( err == ENOENT && make_sub_dirs ){
      string path;
      string_init(&path);
      extract_path(&path,dir_name);
      r = portable_mkdir(path.s,mode,true);
#if HAVE_MKDIR
      if( r == 0 ) r = mkdir(dir_name,mode);
#elif HAVE__MKDIR
#if __WIN32__
      if( r == 0 ) r = _mkdir(dir_name);
#else
      if( r == 0 ) r = _mkdir(dir_name,mode);
#endif
#endif
      err = errno;
      string_destroy(&path);
      errno = err;
      return r;
    }
  }
  return 0;
}
//---------------------------------------------------------------------------
portable_off_t portable_lseek(int handle,portable_off_t offset,int origin)
{
#if HAVE__LSEEKI64
  portable_off_t r = _lseeki64(handle,offset,origin);
#else
  portable_off_t r = lseek(handle,offset,origin);
#endif
  return r;
}
//---------------------------------------------------------------------------
int portable_open(const char * file_name,int flags,mode_t mode,bool make_dirs)
{
  int handle;
#if _MSC_VER
  errno = _sopen_s(&handle,file_name,flags,_SH_DENYNO,_S_IREAD | _S_IWRITE);
#else
  handle = open(file_name,flags,mode);
#endif
  if( handle == -1 && (errno == ENOTDIR || errno == ENOENT) && make_dirs ){
    string path;
    string_init(&path);
    extract_path(&path,file_name);
    portable_mkdir(path.s,mode |
      (mode & (S_IRUSR | S_IWUSR) ? S_IXUSR : 0)
      | (mode & (S_IRGRP | S_IWGRP) ? S_IXGRP : 0)
      | (mode & (S_IROTH | S_IWOTH) ? S_IXOTH : 0),true);
    string_destroy(&path);
#if _MSC_VER
    errno = _sopen_s(&handle,file_name,flags,_SH_DENYNO,_S_IREAD | _S_IWRITE);
#else
    handle = open(file_name,flags,mode);
#endif
  }
  return handle;
}
//---------------------------------------------------------------------------
int portable_unlink(const char * name)
{
#if HAVE__UNLINK
  return _unlink(name);
#else
  return unlink(name);
#endif
}
//---------------------------------------------------------------------------
int portable_access(const char * name,int mode)
{
#if _MSC_VER
  return _access(name,mode);
#else
  return access(name,mode);
#endif
}
//---------------------------------------------------------------------------
int portable_close(int handle)
{
#if HAVE__CLOSE
  int r = _close(handle);
#elif HAVE_CLOSE
  int r = close(handle);
#endif
  return r;
}
//---------------------------------------------------------------------------
portable_ssize_t portable_read(int handle,void * buf,size_t size)
{
#if _MSC_VER
  if( size > ~(unsigned int) 0 ){
    errno = EINVAL;
    return -1;
  }
  return _read(handle,buf,(unsigned int) size);
#elif HAVE__READ
  return _read(handle,buf,size);
#else
  return read(handle,buf,size);
#endif
}
//---------------------------------------------------------------------------
portable_ssize_t portable_write(int handle,const void * buf,size_t size)
{
#if __WIN32__
  if( size > ~(unsigned int) 0 ){
    errno = EINVAL;
    return -1;
  }
  return _write(handle,buf,(unsigned int) size);
#elif HAVE__WRITE
  return _write(handle,buf,size);
#else
  return write(handle,buf,size);
#endif
}
//---------------------------------------------------------------------------
#if HAVE_READV
portable_ssize_t portable_readv(int handle,const struct iovec * iov,int iovcnt)
{
  return readv(handle,iov,iovcnt);
}
#endif
//---------------------------------------------------------------------------
#if HAVE_WRITEV
portable_ssize_t portable_writev(int handle,const struct iovec * iov,int iovcnt)
{
  return writev(handle,iov,iovcnt);
}
#endif
//---------------------------------------------------------------------------
portable_ssize_t portable_pread(int handle,void * buf,size_t size,portable_off_t offset)
{
#if HAVE_PREAD
  return pread(handle,buf,size,offset);
#else
  portable_off_t off = portable_lseek(handle,offset,SEEK_SET);
  if( off >= 0 && off != offset && errno == 0 ){
    errno = EINVAL;
    return -1;
  }
  return portable_read(handle,buf,size);
#endif
}
//---------------------------------------------------------------------------
portable_ssize_t portable_pwrite(int handle,const void * buf,size_t size,portable_off_t offset)
{
#if HAVE_PWRITE
  return pwrite(handle,buf,size,offset);
#else
  portable_off_t off = portable_lseek(handle,offset,SEEK_SET);
  if( off >= 0 && off != offset && errno == 0 ){
    errno = EINVAL;
    return -1;
  }
  return portable_write(handle,buf,size);
#endif
}
//---------------------------------------------------------------------------
#if HAVE_PREADV
portable_ssize_t portable_preadv(int handle,const struct iovec * iov,int iovcnt,portable_off_t offset)
{
  return preadv(handle,iov,iovcnt,offset);
}
#endif
//---------------------------------------------------------------------------
#if HAVE_PWRITEV
portable_ssize_t portable_pwritev(int handle,const struct iovec * iov,int iovcnt,portable_off_t offset)
{
  return pwritev(handle,iov,iovcnt,offset);
}
#endif
//---------------------------------------------------------------------------
int portable_chsize(int handle,portable_off_t size)
{
#if HAVE__CHSIZE_S
  int r = _chsize_s(handle,size);
#elif HAVE_FTRUNCATE
  int r = ftruncate(handle,size);
#else
#error must be implemented
#endif
  return r;
}
//---------------------------------------------------------------------------
portable_off_t portable_filelength(int handle)
{
#if _MSC_VER
  if( handle < 0 ){
    errno = EBADF;
    return -1;
  }
  return _filelengthi64(handle);
#else
  struct stat st;
  if( fstat(handle,&st) != 0 ) return -1;
  return st.st_size;
#endif
}
//---------------------------------------------------------------------------
int portable_fsync(int handle)
{
#if __WIN32__
#if HAVE__GET_OSFHANDLE
  HANDLE h = (HANDLE) _get_osfhandle(handle);
#else
#error must be implemented
#endif
  DWORD err;

  if( h == INVALID_HANDLE_VALUE ){
    errno = EBADF;
    return -1;
  }

  if( !FlushFileBuffers(h) ){
    /* Translate some Windows errors into rough approximations of Unix
     * errors.  MSDN is useless as usual - in this case it doesn't
     * document the full range of errors.
     */
    err = GetLastError();
    switch( err ){
      /* eg. Trying to fsync a tty. */
      case ERROR_INVALID_HANDLE:
        errno = EINVAL;
        break;
      default:
        errno = EIO;
    }
    return -1;
  }
  return 0;
#else
  return fsync(handle);
#endif
}
//---------------------------------------------------------------------------
void portable_sleep(uint64_t timeout)
{
#if HAVE_NANOSLEEP
  struct timespec rqtp;
  rqtp.tv_sec = timeout / 1000000u;
  rqtp.tv_nsec = timeout % 1000000u;
  nanosleep(&rqtp, NULL);
#elif __WIN32__
  Sleep((DWORD) (timeout / 1000u + (timeout < 1000u)));
#elif HAVE_USLEEP
  usleep(timeout / 1000u);
#elif HAVE_SLEEP
  sleep(timeout / 1000000u);
#endif
}
//---------------------------------------------------------------------------
void portable_sleep1()
{
#if HAVE_NANOSLEEP
  struct timespec rqtp;
  rqtp.tv_sec = 0;
  rqtp.tv_nsec = 1;
  nanosleep(&rqtp, NULL);
#elif __WIN32__
  Sleep(1);
#elif HAVE_USLEEP
  usleep(1);
#elif HAVE_SLEEP
  sleep(1);
#endif
}
//---------------------------------------------------------------------------
uintmax_t fibonacci(uintmax_t n)
{
  uintmax_t prev, curr, next;

  if( n == 0 ) return 0;

  prev = 0;
  curr = 1;

  while( --n > 0 ){
    next = curr + prev;
    prev = curr;
    curr = next;
  }

  return curr;
}
//---------------------------------------------------------------------------
uintptr_t portable_gettid()
{
#ifdef __WIN32__
  return (uintptr_t) GetCurrentThreadId();
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__)
  return (uintptr_t) pthread_self();
#elif defined(__linux__)
  return syscall(SYS_gettid);
#endif
}
//---------------------------------------------------------------------------
void set_bit_range(void * bitmap,uintptr_t n,uintptr_t c)
{
  uintptr_t a  = -(intptr_t) n & 7;
  uint8_t mask = (uint8_t) (~0xFFu >> a);

  if( c < a ) mask &= 0xFFu >> (a - c);

  ((uint8_t *) bitmap)[n >> 3] |= mask;

  c -= a;

  if( c > 0 ){

    n += a;

    while( c >= SIZEOF_INTMAX_T * 8 ){
      *(uintmax_t *) ((uint8_t *) bitmap + (n >> 3)) = (uintmax_t) ~(uintmax_t) 0;
      n += SIZEOF_INTMAX_T * 8;
      c -= SIZEOF_INTMAX_T * 8;
    }

#if SIZEOF_INTMAX_T > 4
    if( c >= 4 * 8 ){
      *(uint32_t *) ((uint8_t *) bitmap + (n >> 3)) = (uint32_t) ~(uint32_t) 0;
      n += 4 * 8;
      c -= 4 * 8;
    }
#endif

    if( c >= 2 * 8 ){
      *(uint16_t *) ((uint8_t *) bitmap + (n >> 3)) = (uint16_t) ~(uint16_t) 0;
      n += 2 * 8;
      c -= 2 * 8;
    }

    if( c >= 8 ){
      *((uint8_t *) bitmap + (n >> 3)) = (uint8_t) ~(uint8_t) 0;
      n += 8;
      c -= 8;
    }

    if( c > 0 )
      ((uint8_t *) bitmap)[n >> 3] |= (uint8_t) (0xFFu >> (-(intptr_t) c & 7));
  }
}
//---------------------------------------------------------------------------
void reset_bit_range(void * bitmap,uintptr_t n,uintptr_t c)
{
  uintptr_t a  = -(intptr_t) n & 7;
  uint8_t mask = (uint8_t) (~0xFFu >> a);

  if( c < a ) mask &= 0xFFu >> (a - c);

  ((uint8_t *) bitmap)[n >> 3] &= (uint8_t) ~mask;

  c -= a;

  if( c > 0 ){

    n += a;

    while( c >= SIZEOF_INTMAX_T * 8 ){
      *(uintmax_t *) ((uint8_t *) bitmap + (n >> 3)) = 0;
      n += SIZEOF_INTMAX_T * 8;
      c -= SIZEOF_INTMAX_T * 8;
    }

#if SIZEOF_INTMAX_T > 4
    if( c >= 4 * 8 ){
      *(uint32_t *) ((uint8_t *) bitmap + (n >> 3)) = 0;
      n += 4 * 8;
      c -= 4 * 8;
    }
#endif

    if( c >= 2 * 8 ){
      *(uint16_t *) ((uint8_t *) bitmap + (n >> 3)) = 0;
      n += 2 * 8;
      c -= 2 * 8;
    }

    if( c >= 8 ){
      *((uint8_t *) bitmap + (n >> 3)) = 0;
      n += 8;
      c -= 8;
    }

    if( c > 0 )
      ((uint8_t *) bitmap)[n >> 3] &= (uint8_t) (~(0xFFu >> (-(intptr_t) c & 7)));
  }
}
//---------------------------------------------------------------------------
void invert_bit_range(void * bitmap,uintptr_t n,uintptr_t c)
{
  uintptr_t a  = -(intptr_t) n & 7;
  uint8_t mask = (uint8_t) (~0xFFu >> a);

  if( c < a ) mask &= 0xFFu >> (a - c);

  ((uint8_t *) bitmap)[n >> 3] |= (uint8_t) (~((uint8_t *) bitmap)[n >> 3] & mask);

  c -= a;

  if( c > 0 ){

    n += a;

    while( c >= SIZEOF_INTMAX_T * 8 ){
      *(uintmax_t *) ((uint8_t *) bitmap + (n >> 3)) = (uintmax_t) ~*(uintmax_t *) ((uint8_t *) bitmap + (n >> 3));
      n += SIZEOF_INTMAX_T * 8;
      c -= SIZEOF_INTMAX_T * 8;
    }

#if SIZEOF_INTMAX_T > 4
    if( c >= 4 * 8 ){
      *(uint32_t *) ((uint8_t *) bitmap + (n >> 3)) = (uint32_t) (~*(uint32_t *) ((uint8_t *) bitmap + (n >> 3)));
      n += 4 * 8;
      c -= 4 * 8;
    }
#endif

    if( c >= 2 * 8 ){
      *(uint16_t *) ((uint8_t *) bitmap + (n >> 3)) = (uint16_t) (~*(uint16_t *) ((uint8_t *) bitmap + (n >> 3)));
      n += 2 * 8;
      c -= 2 * 8;
    }

    if( c >= 8 ){
      *((uint8_t *) bitmap + (n >> 3)) = (uint8_t) (~*(uint8_t *) ((uint8_t *) bitmap + (n >> 3)));
      n += 8;
      c -= 8;
    }

    if( c > 0 )
      ((uint8_t *) bitmap)[n >> 3] |= (uint8_t) (~((uint8_t *) bitmap)[n >> 3] & ~(0xFFu >> (-(intptr_t) c & 7)));
  }
}
//-----------------------------------------------------------------------------
uintptr_t system_cores()
{
  uintptr_t n = 1;
#ifdef __WIN32__
  SYSTEM_INFO si;
  GetSystemInfo(&si);
  n = si.dwNumberOfProcessors;
#elif HAVE_SYSCONF && _SC_NPROCESSORS_CONF
  n = sysconf(_SC_NPROCESSORS_CONF);
#endif
  return n;
}
//-----------------------------------------------------------------------------
